package org.chipu.jnids.beans;

import static org.chipu.jnids.sce.profile.StatType.Type.*;
import java.net.InetAddress;
import java.util.*;
import org.chipu.jnids.sancp.ProfileCreator;
import org.chipu.jnids.sce.profile.*;
import org.chipu.shared.util.singletons.*;

public final class Snapshot {
	public final int timeInSeconds;
	public final Set<Flow> connections;
	private transient PuntualStats stats;

	public Snapshot(int timeInSeconds) {
		this(timeInSeconds, Collections.<Flow> emptySet());
	}

	public Snapshot(int timeInSeconds, Set<Flow> connections) {
		if (connections == null)
			throw new NullPointerException();
		this.timeInSeconds = timeInSeconds;
		this.connections = Collections.unmodifiableSet(connections);
	}

	private PuntualStats createStats() {
		Set<Flow> connectionsIn = new HashSet<Flow>(connections.size());
		Set<Flow> connectionsOut = new HashSet<Flow>(connections.size());
		Map<InetAddress, Integer> connsToInternal = new HashMap<InetAddress, Integer>(ProfileCreator.snapshotAddressSize);
		Map<InetAddress, Integer> connsFromInternal = new HashMap<InetAddress, Integer>(ProfileCreator.snapshotAddressSize);
		Map<InetAddress, Integer> connsToExternal = new HashMap<InetAddress, Integer>(ProfileCreator.snapshotAddressSize);
		Map<InetAddress, Integer> connsFromExternal = new HashMap<InetAddress, Integer>(ProfileCreator.snapshotAddressSize);
		for(Flow f : connections) {
			boolean fromLAN = f.isFromInside();
			boolean toLAN = f.isToInside();
			assert fromLAN || toLAN;
			if (fromLAN && toLAN)
				// internal traffic
				continue;
			if (fromLAN) {// outgoing
				connectionsOut.add(f);
				CollectionUtils.addCountToMap(connsFromInternal, f.orgAddress.getAddress());
				CollectionUtils.addCountToMap(connsToExternal, f.destAddress.getAddress());
			} else {// ingoing
				connectionsIn.add(f);
				CollectionUtils.addCountToMap(connsFromExternal, f.orgAddress.getAddress());
				CollectionUtils.addCountToMap(connsToInternal, f.destAddress.getAddress());
			}
		}
		// using network protocols
		int c = (int) (NetworkUtils.getKnownNetworkProtocolsCount() / 0.75) + 1;
		Map<Byte, Integer> nConnectionsInByProt = new HashMap<Byte, Integer>(c);
		Map<Byte, Integer> nConnectionsOutByProt = new HashMap<Byte, Integer>(c);
		for(Byte b : NetworkUtils.getKnownNetworkProtocols()) {
			nConnectionsInByProt.put(b, JnidsUtils.getNConnections(connectionsIn, b));
			nConnectionsOutByProt.put(b, JnidsUtils.getNConnections(connectionsOut, b));
		}

		// PERFORMANCE convert timeInSeconds into timeUTC
		PuntualStats ps = new PuntualStats(timeInSeconds);
		ps.add(StatType.getStatType(CONNECTIONS_IN), Stats.getStats(connectionsIn.size()));
		ps.add(StatType.getStatType(CONNECTIONS_OUT), Stats.getStats(connectionsOut.size()));
		for(Map.Entry<Byte, Integer> entry : nConnectionsInByProt.entrySet())
			ps.add(new StatType(CONNECTIONS_IN_BY_PROTOCOL, entry.getKey()), Stats.getStats(entry.getValue()));
		for(Map.Entry<Byte, Integer> entry : nConnectionsOutByProt.entrySet())
			ps.add(new StatType(CONNECTIONS_OUT_BY_PROTOCOL, entry.getKey()), Stats.getStats(entry.getValue()));

		// PERFORMANCE
		ps.add(StatType.getStatType(CONNECTIONS_FROM_EXTERNAL), Stats.getStats(new ArrayList<Integer>(connsToInternal.values())));
		ps.add(StatType.getStatType(CONNECTIONS_TO_EXTERNAL), Stats.getStats(new ArrayList<Integer>(connsToInternal.values())));
		ps.add(StatType.getStatType(CONNECTIONS_TO_INTERNAL), Stats.getStats(new ArrayList<Integer>(connsToInternal.values())));
		ps.add(StatType.getStatType(CONNECTIONS_FROM_INTERNAL), Stats.getStats(new ArrayList<Integer>(connsToInternal.values())));
		return ps;
	}

	public Stats getStats(StatType type) {
		if (stats == null)
			stats = createStats();
		return stats.get(type);
	}

	/** @return a map containing the most active hosts and the number of connection associated */
	public Map<InetAddress, Integer> getMostActiveHosts(StatType st) {
		assert st.type == CONNECTIONS_FROM_INTERNAL || st.type == CONNECTIONS_TO_INTERNAL || st.type == CONNECTIONS_FROM_EXTERNAL
			|| st.type == CONNECTIONS_TO_EXTERNAL;
		// Number of connections by host
		Map<InetAddress, Integer> map = new HashMap<InetAddress, Integer>((int) (ProfileCreator.snapshotAddressSize / 0.75) + 1);
		switch(st.type) {
		case CONNECTIONS_FROM_INTERNAL:
		case CONNECTIONS_FROM_EXTERNAL:
			for(Flow f : connections)
				CollectionUtils.addCountToMap(map, f.orgAddress.getAddress());
			break;
		case CONNECTIONS_TO_INTERNAL:
		case CONNECTIONS_TO_EXTERNAL:
			for(Flow f : connections)
				CollectionUtils.addCountToMap(map, f.destAddress.getAddress());
			break;
		default:
			throw new IllegalArgumentException("StatType.Type not valid: " + st.type);
		}
		Stats stats = getStats(st);
		// limit to consider the % more active hosts
		float limit = stats.average + 2 * stats.standardDeviation;
		Map<InetAddress, Integer> rta = new HashMap<InetAddress, Integer>(ProfileCreator.snapshotActiveAddressSize);
		for(Map.Entry<InetAddress, Integer> e : map.entrySet())
			if (e.getValue() > limit)
				rta.put(e.getKey(), e.getValue());
		return rta;
	}
	// public Map<InetAddress, Integer> getMostActiveOutgoingInternalHosts() {
	// return getMostActiveHosts(StatType.getStatType(CONNECTIONS_FROM_INTERNAL));
	// }
	//
	// public Map<InetAddress, Integer> getMostActiveIngoingInternalHosts() {
	// return getMostActiveHosts(StatType.getStatType(CONNECTIONS_TO_INTERNAL));
	// }
	//
	// public Map<InetAddress, Integer> getMostActiveOutgoingExternalHosts() {
	// return getMostActiveHosts(StatType.getStatType(CONNECTIONS_FROM_EXTERNAL));
	// }
	//
	// public Map<InetAddress, Integer> getMostActiveIngoingExternalHosts() {
	// return getMostActiveHosts(StatType.getStatType(CONNECTIONS_TO_EXTERNAL));
	// }
}